gusucode.com > VC++ EMF图片浏览器(可读emf、wmf、emz、wmz、png……等)-源码程序 > VC++ EMF图片浏览器(可读emf、wmf、emz、wmz、png……等)-源码程序/code/Src/Client/scemflib/SCGdiplusUtils.cpp
//Download by http://www.NewXing.com /* * This file is part of the EMFexplorer projet. * Copyright (C) 2004 Smith Charles. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * Extension: for commercial use, apply the Equity Public License, which * adds to the normal terms of the GLPL a condition of donation to the author. * If you are interested in support for this source code, * contact Smith Charles <smith.charles@free.fr> for more information. */ #include "stdafx.h" #include "SCGdiplusUtils.h" #include "SCEMF.h" #include "SCEMFRasterizer.h" #include "SCFileTypes.h" #include "SCGenInclude.h" #include SC_INC_WINLIB(SCMemDC.h) #include SC_INC_WINLIB(SCBitmap.h) #include SC_INC_WINLIB(MSDib.h) #include SC_INC_WINLIB(SCGDIUtils.h) #include SC_INC_GENLIB(SCGenMath.h) #include <float.h> // GDI+ won't allow DEBUG_NEW to work // #ifdef _DEBUG // #define new DEBUG_NEW // #undef THIS_FILE // static char THIS_FILE[] = __FILE__; // #endif /// /// Create a GDI+ bitmap from a packed DIB (a pointer to a /// BITMAPINFO structure immediately followed by the array of bytes that define /// the pixels of the bitmap). /// Bitmap* SCBitmapFromPtr(LPCBYTE pBytes) { ASSERT(pBytes); if (!pBytes) return NULL; // get the BITMAPINFO BITMAPINFO* pBmi = (BITMAPINFO*)pBytes; // Locate the bits after the color table: // Since the DIB is packed, the biClrUsed member must be either zero or // the actual size of the color table BYTE* pBits = (BYTE*)pBmi + pBmi->bmiHeader.biSize + pBmi->bmiHeader.biClrUsed; // create GDI+ object Bitmap* pBitmap = new Bitmap(pBmi, pBits); if (!pBitmap || (pBitmap->GetLastStatus()!=Ok)) { ASSERT(0); delete pBitmap; return NULL; } return pBitmap; } /// /// Create a GDI+ bitmap from a packed DIB (a global memory chunk pointing to a /// BITMAPINFO structure immediately followed by the array of bytes that define /// the pixels of the bitmap). /// Note: on 32-bit systems, the HGLOBAL is not quite different from a pointer /// Bitmap* SCBitmapFromHGLOBAL(HGLOBAL hMem, SCShortDCState& rDCState) { ASSERT(hMem); if (!hMem) return NULL; // get the BITMAPINFO BITMAPINFO* pBmi = (BITMAPINFO*)hMem; //GlobalLock(hMem); // Locate the bits after the color table: // Since the DIB is packed, the biClrUsed member must be either zero or // the actual size of the color table INT iNbColors = DIBNumColors(LPBITMAPINFOHEADER(pBmi)); BYTE* pBits = (BYTE*)pBmi + pBmi->bmiHeader.biSize + iNbColors*sizeof(RGBQUAD); // create GDI+ object Bitmap* pBitmap = NULL; if (32==pBmi->bmiHeader.biBitCount) pBitmap = new Bitmap(pBmi->bmiHeader.biWidth, pBmi->bmiHeader.biHeight, 4*pBmi->bmiHeader.biWidth, PixelFormat32bppARGB, pBits); else { pBitmap = new Bitmap(pBmi, pBits); INT iRop2 = rDCState.dwRop2; // Update color palette for 1-bpp. if (1==pBmi->bmiHeader.biBitCount) { RGBQUAD* pColors; RGBQUAD MonoColors[2]; if (rDCState.bMonoDC) {// Slight difference for DIB? Is it ONLY when the DC IS MONOCHROME that // background and text colors come into play? // "When an application selects a two-color DIB pattern brush into a monochrome // device context, Windows ignores the colors specified in the DIB and instead // displays the pattern brush using the current text and background colors of // the device context. Pixels mapped to the first color // (at offset 0 in the DIB color table) of the DIB are displayed using the // text color. Pixels mapped to the second color (at offset 1 in the color table) // are displayed using the background color." pColors = (RGBQUAD*)&MonoColors; SCFillMonochromePalette(rDCState, (PPALETTEENTRY)pColors); } else {// ignore background and text colors, and use the DIB color table pColors = (RGBQUAD*)&pBmi->bmiColors; } #define SC_ISWHITE_RGBQUAD(q) (255==q.rgbRed && 255==q.rgbGreen && 255==q.rgbBlue) ColorPalette* pPal = (ColorPalette*)new BYTE[sizeof(ColorPalette) + iNbColors*sizeof(ARGB)]; pPal->Flags = PaletteFlagsHasAlpha; pPal->Count = iNbColors; INT iAlpha = 255; INT i; switch (iRop2) { case R2_MASKPEN: // The normal code is "black=>opaque, and white=>transparent". But we // make black pixels half-black and make white pixels transparent. // take it as some hand-tuned "gamma corection". for (i=0; (i<iNbColors); i++) { if (SC_ISWHITE_RGBQUAD(pColors[i])) iAlpha = 0; // fully transparent; else iAlpha = 255; // fully opaque (128 works better; why?) pPal->Entries[i] = Color::MakeARGB(iAlpha, pColors[i].rgbRed, pColors[i].rgbGreen, pColors[i].rgbBlue); } break; default: // normal palette for (i=0; (i<iNbColors); i++) { pPal->Entries[i] = Color::MakeARGB(255, pColors[i].rgbRed, pColors[i].rgbGreen, pColors[i].rgbBlue); } } pBitmap->SetPalette(pPal); delete [] pPal; } } //GlobalUnlock(hMem); if (pBitmap && (pBitmap->GetLastStatus()!=Ok)) { ASSERT(0); delete pBitmap; return NULL; } return pBitmap; } /// /// Create a GDI+ bitmap from a handle to a Windows bitmap. /// Bitmap* SCBitmapFromMemBmp(HBITMAP hBM) { ASSERT(hBM); // GDI+ 1.0: we don't have a simple constructor Bitmap::Bitmap(HBITMAP) // But a palette residing outside the bitmap was carefully cast in. Jeez! Bitmap* pBitmap = new Bitmap(hBM, (HPALETTE)NULL); if (pBitmap && (pBitmap->GetLastStatus()!=Ok)) { ASSERT(0); delete pBitmap; return NULL; } return pBitmap; } /// /// Create a two-color palette to use with the monochrome brush currently selected into a DC. /// "A brush created by using a monochrome (1 bit per pixel) bitmap has /// the text and background colors of the device context to which it is drawn. /// Pixels represented by a 0 bit are drawn with the current text color; /// pixels represented by a 1 bit are drawn with the current background color." /// HPALETTE SCCreatePatternPalette(SCShortDCState& rDCState) { // Create the logical palette based on the entries // Allocate memory for the palette. LPLOGPALETTE lpLogPal = (LPLOGPALETTE)new BYTE [sizeof(LOGPALETTE) + 2*sizeof(PALETTEENTRY)]; if (!lpLogPal) return NULL; // Initialize. lpLogPal->palVersion = 0x300; lpLogPal->palNumEntries = 2; // Update colors in palette. SCFillMonochromePalette(rDCState, lpLogPal->palPalEntry); // Create the palette. HPALETTE hPal = CreatePalette(lpLogPal); // Clean up. delete [] (BYTE*)lpLogPal; return hPal; } /// /// Create a two-color bitmap representing a monochrome brush. /// "A brush created by using a monochrome (1 bit per pixel) bitmap has /// the text and background colors of the device context to which it is drawn. /// Pixels represented by a 0 bit are drawn with the current text color; /// pixels represented by a 1 bit are drawn with the current background color." /// // Direct method, with external palette Bitmap* SCPatternFromMemBmp(HBITMAP hBM, SCShortDCState& rDCState) { ASSERT(hBM); rDCState.bMonoBrush = FALSE; #if 0 // Won't work: GDI+ ignores the palette this time (try to understand) HPALETTE hPal = SCCreatePatternPalette(rDCState); ASSERT(hPal); Bitmap* pBitmap = new Bitmap(hBM, hPal); if (!pBitmap) return NULL; if (pBitmap->GetLastStatus()!=Ok) { ASSERT(0); delete pBitmap; pBitmap = NULL; } DeleteObject(hPal); #else BITMAP bm; int iRes = GetObject(hBM, sizeof(BITMAP), &bm); ASSERT(iRes); // Trying to set a GDI+ palette after bitmap creation Bitmap* pBitmap = new Bitmap(hBM, (HPALETTE)NULL); if (!pBitmap) return NULL; if (pBitmap->GetLastStatus()!=Ok) { ASSERT(0); delete pBitmap; return NULL; } if (bm.bmBitsPixel!=1) return pBitmap; rDCState.bMonoBrush = TRUE; // Update color palette for 1-bpp. if (!SCSetMonochromeBrushPalette(pBitmap, rDCState)) { ASSERT(0); delete pBitmap; pBitmap = NULL; } #endif return pBitmap; } /// /// Create a GDI+ brush from a GDI LOGBRUSH. /// Brush* SCBrushFromLogBrush(LOGBRUSH& rLogBrush, SCShortDCState& rDCState) { Brush* pBrush = NULL; rDCState.bMonoBrush = FALSE; switch (rLogBrush.lbStyle) { case BS_SOLID: {// Solid brush Color BrushColor; BrushColor.SetFromCOLORREF(rLogBrush.lbColor); pBrush = new SolidBrush(BrushColor); } break; case BS_HATCHED: {// Hatched brush HatchStyle hatchStyle; switch (rLogBrush.lbHatch) { case HS_BDIAGONAL: hatchStyle = HatchStyleBackwardDiagonal; break; case HS_CROSS: hatchStyle = HatchStyleCross; break; case HS_DIAGCROSS: hatchStyle = HatchStyleDiagonalCross; break; case HS_FDIAGONAL: hatchStyle = HatchStyleForwardDiagonal; break; case HS_HORIZONTAL: hatchStyle = HatchStyleHorizontal; break; case HS_VERTICAL: hatchStyle = HatchStyleVertical; break; default: ASSERT(0); // not reached } Color FgColor; FgColor.SetFromCOLORREF(rLogBrush.lbColor); // Background color is controlled by current BkColor and BkMode in DC COLORREF ColorRef = rDCState.crBkColor; BYTE bAlpha = (TRANSPARENT==rDCState.dwBkMode) ? 0 : 255; Color BkColor(bAlpha, GetRValue(ColorRef), GetGValue(ColorRef), GetBValue(ColorRef)); pBrush = new HatchBrush(hatchStyle, FgColor, BkColor); } break; case BS_PATTERN: case BS_PATTERN8X8: {// Patterned brush (memory bitmap) referenced by a HBITMAP in lbHatch // "A brush created by using a monochrome (1 bit per pixel) bitmap has // the text and background colors of the device context to which it is drawn. // Pixels represented by a 0 bit are drawn with the current text color; // pixels represented by a 1 bit are drawn with the current background color." // Bitmap* pBitmap = SCPatternFromMemBmp((HBITMAP)rLogBrush.lbHatch, rDCState); if (pBitmap) { pBrush = new TextureBrush(pBitmap); delete pBitmap; } } break; case BS_DIBPATTERNPT: {// Patterned brush (packed DIB) referenced by a pointer in lbHatch Bitmap* pBitmap = SCBitmapFromPtr((LPBYTE)rLogBrush.lbHatch); if (pBitmap) { pBrush = new TextureBrush(pBitmap); delete pBitmap; } } break; case BS_DIBPATTERN: case BS_DIBPATTERN8X8: {// Patterned brush (packed DIB) referenced by an HGLOBAL in lbHatch Bitmap* pBitmap = SCBitmapFromHGLOBAL((HGLOBAL)rLogBrush.lbHatch, rDCState); if (pBitmap) { pBrush = new TextureBrush(pBitmap); delete pBitmap; } } break; case BS_NULL: // pBrush = NULL; // already done break; case BS_INDEXED: case BS_MONOPATTERN: // Undocumented (maybe driver specific) ASSERT(0); break; default: ASSERT(0); } ASSERT(!pBrush || (pBrush->GetLastStatus()==Ok)); return pBrush; } /// /// Create a GDI+ brush from a GDI LOGPEN. /// Pen* SCPenFromLogPen(LOGPEN& rLogPen) { if (PS_NULL==rLogPen.lopnStyle) // no bit masking is required return NULL; Color PenColor; PenColor.SetFromCOLORREF(rLogPen.lopnColor); Pen* pPen = new Pen(PenColor, (REAL)rLogPen.lopnWidth.x); // Line style switch (rLogPen.lopnStyle) { case PS_SOLID: pPen->SetDashStyle(DashStyleSolid); break; case PS_DASH: pPen->SetDashStyle(DashStyleDash); break; case PS_DOT: pPen->SetDashStyle(DashStyleDot); break; case PS_DASHDOT: pPen->SetDashStyle(DashStyleDashDot); break; case PS_DASHDOTDOT: pPen->SetDashStyle(DashStyleDashDotDot); break; case PS_INSIDEFRAME: // The pen is solid, geometric pPen->SetDashStyle(DashStyleSolid); // GDI:"When this pen is used in any graphics device interface (GDI) drawing // function that takes a bounding rectangle, the dimensions of the figure // are shrunk so that it fits entirely in the bounding rectangle, taking into // account the width of the pen." // GDI+:"PenAlignmentInset Specifies, when drawing a polygon, // that the pen is aligned on the inside of the edge of the polygon." pPen->SetAlignment(PenAlignmentInset); // One of the documentations is clearer than the other: make your choice. break; case PS_ALTERNATE: // fall through default: ASSERT(0); } // Investigate to find out if a default GDI+ pen should be considered GEOMETRIC. // If so apply default join and cap styles #pragma message( __FILE__ "(449): TODO: Review default GDI+ pen") #if 0 // Default values (cf. MSDN "Pens in Win32") // Lines joint (applies only to PS_GEOMETRIC pens in GDI) pPen->SetLineJoin(LineJoinRound); // End cap (applies only to PS_GEOMETRIC pens in GDI) pPen->SetLineCap(LineCapRound, LineCapRound, DashCapRound); #endif ASSERT(!pPen || (pPen->GetLastStatus()==Ok)); return pPen; } /// /// Create a GDI+ brush from a GDI EXTLOGPEN. /// Pen* SCPenFromExtLogPen(EXTLOGPEN* pExtLogPen, SCShortDCState& rDCState) { ASSERT(pExtLogPen); INT iPenStyle = pExtLogPen->elpPenStyle & PS_STYLE_MASK; // style bits masking is required if (PS_NULL==iPenStyle) return NULL; // Note: don't know how to tell GDI+ about PS_COSMETIC or PS_GEOMETRIC pens INT iPenType = pExtLogPen->elpPenStyle & PS_TYPE_MASK; // Default pen width and color INT iWidth = (PS_GEOMETRIC==iPenType) ? pExtLogPen->elpWidth : 1; Color PenColor; PenColor.SetFromCOLORREF(pExtLogPen->elpColor); Pen* pPen = new Pen(PenColor, (REAL)iWidth); // Line style switch (iPenStyle) { case PS_SOLID: pPen->SetDashStyle(DashStyleSolid); break; case PS_DASH: pPen->SetDashStyle(DashStyleDash); break; case PS_DOT: pPen->SetDashStyle(DashStyleDot); break; case PS_DASHDOT: pPen->SetDashStyle(DashStyleDashDot); break; case PS_DASHDOTDOT: pPen->SetDashStyle(DashStyleDashDotDot); break; case PS_USERSTYLE: pPen->SetDashStyle(DashStyleCustom); // user-supplied style array pointed to by elpStyleEntry (limited to // elpNumEntries elements) must be used to describe the custom pen { REAL* pReals = SCREALFromDWORD(pExtLogPen->elpStyleEntry, pExtLogPen->elpNumEntries); // "The length of each dash and space in the dash pattern is the product of // the element value in the array and the width of the Pen object." // So now we must devide somehow. // But I'm not sure the method coded here is bullet proof, since I should // use what GDI calls "style units" to make the division. But listen: // "If you absolutely have to know the style unit, the application must draw a styled // line in a bitmap and read it back to determine the size of the style units // in the x and y directions". if (0==iWidth) iWidth = 1; for (UINT i=0; (i<pExtLogPen->elpNumEntries); i++) { pReals[i] = pReals[i]/(REAL)iWidth; } pPen->SetDashPattern(pReals, pExtLogPen->elpNumEntries); delete [] pReals; } break; case PS_INSIDEFRAME: // comments in SCPenFromLogPen pPen->SetDashStyle(DashStyleSolid); pPen->SetAlignment(PenAlignmentInset); break; case PS_ALTERNATE: // Windows NT/2000/XP: we don't have the corresponding style in GDI+, so just pray. // fall through default: pPen->SetDashStyle(DashStyleSolid); ASSERT(0); } if (PS_GEOMETRIC==iPenType) { // Lines joint (applies only to PS_GEOMETRIC pens in GDI) switch (pExtLogPen->elpPenStyle & PS_JOIN_MASK) { case PS_JOIN_ROUND: pPen->SetLineJoin(LineJoinRound); break; case PS_JOIN_MITER: pPen->SetLineJoin(LineJoinMiter); break; case PS_JOIN_BEVEL: pPen->SetLineJoin(LineJoinBevel); break; default: // (cf. MSDN "Pens in Win32") pPen->SetLineJoin(LineJoinRound); ASSERT(0); } // End cap (applies only to PS_GEOMETRIC pens in GDI) switch (pExtLogPen->elpPenStyle & PS_ENDCAP_MASK) { case PS_ENDCAP_ROUND: pPen->SetLineCap(LineCapRound, LineCapRound, DashCapRound); break; case PS_ENDCAP_FLAT: pPen->SetLineCap(LineCapFlat, LineCapFlat, DashCapFlat); break; case PS_ENDCAP_SQUARE: pPen->SetLineCap(LineCapSquare, LineCapSquare, DashCapFlat); break; default: // (cf. MSDN "Pens in Win32") pPen->SetLineCap(LineCapRound, LineCapRound, DashCapRound); ASSERT(0); } // "all [brush related] members must be used to specify the brush // attributes of the pen". if ((BS_HOLLOW!=pExtLogPen->elpBrushStyle) && (BS_SOLID!=pExtLogPen->elpBrushStyle || PS_SOLID!=iPenStyle)) { LOGBRUSH LogBrush; LogBrush.lbStyle = pExtLogPen->elpBrushStyle; LogBrush.lbColor = pExtLogPen->elpColor; LogBrush.lbHatch = pExtLogPen->elpHatch; BOOL bSvd = rDCState.bMonoBrush; Brush* pBrush = SCBrushFromLogBrush(LogBrush, rDCState); if (pBrush) pPen->SetBrush(pBrush); rDCState.bMonoBrush = bSvd; } } // else PS_COSMETIC: // "the [pExtLogPen->elpColor] member specifies the color of the pen and the // [pExtLogPen->elpPenStyle] member must be set to BS_SOLID" return pPen; } /// /// Fill a two-color palette with current text/background colors of DC. /// (For monochrome brush color realization) /// void SCFillMonochromePalette(SCShortDCState& rDCState, PPALETTEENTRY palPalEntry) { ASSERT(palPalEntry); COLORREF crBkColor = rDCState.crBkColor; COLORREF crTextColor = rDCState.crTextColor; palPalEntry[0].peRed = GetRValue(crTextColor); palPalEntry[0].peGreen = GetGValue(crTextColor); palPalEntry[0].peBlue = GetBValue(crTextColor); palPalEntry[0].peFlags = 0; palPalEntry[1].peRed = GetRValue(crBkColor); palPalEntry[1].peGreen = GetGValue(crBkColor); palPalEntry[1].peBlue = GetBValue(crBkColor); palPalEntry[1].peFlags = 0; } /// /// Attach a two-color palette to a GDI+ image, using the given colors. /// inline BOOL SCSetMonochromeImagePalette(Image* pImage, COLORREF crTextColor, COLORREF crBkColor) { ASSERT(pImage); BOOL bOk = FALSE; // Update color palette for 1-bpp. ColorPalette* pPal = (ColorPalette*)new BYTE[sizeof(ColorPalette) + 2*sizeof(ARGB)]; pPal->Flags = PaletteFlagsHasAlpha; pPal->Count = 2; pPal->Entries[0] = Color::MakeARGB(255, GetRValue(crTextColor), GetGValue(crTextColor), GetBValue(crTextColor)); pPal->Entries[1] = Color::MakeARGB(255, GetRValue(crBkColor), GetGValue(crBkColor), GetBValue(crBkColor)); bOk = (pImage->SetPalette(pPal)==Ok); delete [] (BYTE*)pPal; return bOk; } /// /// Attach a two-color palette to a GDI+ image, using current text and background color. /// inline BOOL SCSetMonochromeBrushPalette(Image* pImage, SCShortDCState& rDCState) { ASSERT(pImage); return SCSetMonochromeImagePalette(pImage, rDCState.crTextColor, rDCState.crBkColor); } /// /// Attach a two-color palette to a GDI+ image, using the DC state colors. /// Brush* SCRealizeMonochromeBrushPalette(Brush* pBrush, SCShortDCState& rDCState) { ASSERT(pBrush && pBrush->GetType()==BrushTypeTextureFill); if (!pBrush || pBrush->GetType()!=BrushTypeTextureFill) return NULL; // Well, the bitmap stored in the brush isn't 1bpp anymore // And setting palette on 32bpp bitmap will silently fail Bitmap* pBitmap = (Bitmap*)((TextureBrush*)pBrush)->GetImage(); // a 32bpp (a plain copy, not a "just to see" pointer) ASSERT(pBitmap && pBitmap->GetLastStatus()==Ok); #if 0 // This too fails. "Jeez!... it's full of 'stars!'" Bitmap* pClone = pBitmap->Clone(0, 0, pBitmap->GetWidth(), pBitmap->GetHeight(), PixelFormat1bppIndexed); #else // Manual cloning => bad/funny results // LockBits doesn't understand that WE WANT PixelFormat1bppIndexed! // "LockBits [...] // format // [in] Integer that specifies the format of the pixel data in the temporary buffer. // The pixel format of the temporary buffer does not have to be the same as the pixel // format of this Bitmap object. The PixelFormat data type and constants that represent // various pixel formats are defined in Gdipluspixelformats.h. For more information about // pixel format constants, see Image Pixel Format Constants. // Microsoft Windows GDI+ version 1.0 does not support processing of 16-bits-per-channel // images, so you should not set this parameter equal to PixelFormat48bppRGB, // PixelFormat64bppARGB, or PixelFormat64bppPARGB." // You can add PixelFormat1bppIndexed to the list. BitmapData BmData; Rect rect(0, 0, pBitmap->GetWidth(), pBitmap->GetHeight()); Status iRes = pBitmap->LockBits(&rect, ImageLockModeRead, PixelFormat32bppARGB/*oops!*/, &BmData); ASSERT(iRes==Ok); Bitmap* pClone = new Bitmap(pBitmap->GetWidth(), pBitmap->GetHeight(), BmData.Stride, PixelFormat1bppIndexed, (BYTE*)BmData.Scan0); pBitmap->UnlockBits(&BmData); #endif ASSERT(pBitmap->GetLastStatus()==Ok); ASSERT(pClone && pClone->GetLastStatus()==Ok); BOOL bOk = SCSetMonochromeImagePalette(pClone, rDCState.crTextColor, rDCState.crBkColor); TextureBrush* pNewBrush = NULL; if (bOk) { // We can't just set //((TextureBrush*)pBrush)->SetBitmap(pClone); Matrix matrix; ((TextureBrush*)pBrush)->GetTransform(&matrix); pNewBrush = new TextureBrush(pClone); if (pNewBrush) { Status iRes = pNewBrush->SetTransform(&matrix); ASSERT(iRes==Ok); } } delete pClone; delete pBitmap; // warning!? return pNewBrush; } #if 0 /// /// Create a GDI+ font from a GDI LOGFONT. /// Font* SCFontFromLogFont(LOGFONT& rLogFont) { ASSERT(0); // not implemented return NULL; } #endif /// /// Build an TrueType approximation of the font describes in LogFont. /// Note, this is a late font substitution (more complicated). /// HFONT SCTTFontFromLOGFONT(LOGFONT& rLogFont, HDC hDC) { rLogFont.lfOutPrecision = OUT_TT_ONLY_PRECIS; rLogFont.lfPitchAndFamily |= SCFontFamilyApproximant((TCHAR*)rLogFont.lfFaceName); HFONT hFont = CreateFontIndirect(&rLogFont); ASSERT(hFont); if (!hFont) {// silent failure switch (rLogFont.lfPitchAndFamily & 0xF0) { case FF_ROMAN: _tcscpy((TCHAR*)rLogFont.lfFaceName, _T("Times New Roman")); break; case FF_DONTCARE: case FF_SWISS: case FF_MODERN: case FF_SCRIPT: case FF_DECORATIVE: default: _tcscpy((TCHAR*)rLogFont.lfFaceName, _T("Arial")); } return NULL; } BOOL bTmpDC = (NULL==hDC); if (bTmpDC) hDC = ::GetDC(NULL); ASSERT(hDC); HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); GetTextFace(hDC, LF_FACESIZE, rLogFont.lfFaceName); if (bTmpDC) {// no DC was given for selecting font SelectObject(hDC, hOldFont); ReleaseDC(NULL,hDC); } // else the returned font is selected in the DC // the danger is we are losing hOldFont return hFont; } /// /// Create an array of GDI+ Points from an array of Win32 POINTs. /// // Point* SCPointFromPOINT(LPCPOINT pPoints, DWORD dwCount) template <class T> Point* SCPointFromPOINT(T pPoints, DWORD dwCount) { ASSERT(pPoints && dwCount); Point *pPts = new Point[dwCount]; Point *pDest = pPts; Point *pLimit = pDest + dwCount; for(; (pDest<pLimit); pDest++, pPoints++) { pDest->X = pPoints->x; pDest->Y = pPoints->y; } return pPts; } // some explicit instantiations, cause it's a library template Point* SCPointFromPOINT<LPPOINT>(LPPOINT, DWORD); template Point* SCPointFromPOINT<LPPOINTS>(LPPOINTS, DWORD); // /// /// Create an array of GDI+ Points from an array of Win32 POINTSs completed by the /// current position in play DC (hold in rPtStart). /// template <class T> Point* SCPointFromPOINTTo(Point& rPtStart, T pPoints, DWORD dwCount) { ASSERT(pPoints && dwCount); // add +1 to count to include the current position in DC Point *pPts = new Point[++dwCount]; // copy first point pPts->X = rPtStart.X; pPts->Y = rPtStart.Y; // copy other points Point *pDest = pPts + 1; Point *pLimit = pPts + dwCount; for(; (pDest<pLimit); pDest++, pPoints++) { pDest->X = pPoints->x; pDest->Y = pPoints->y; } return pPts; } // some explicit instantiations, cause it's a library template Point* SCPointFromPOINTTo<LPPOINT>(Point&, LPPOINT, DWORD); template Point* SCPointFromPOINTTo<LPPOINTS>(Point&, LPPOINTS, DWORD); // /// /// Create an array of REALs from an array of DWORDs. /// REAL* SCREALFromDWORD(LPDWORD pDWords, DWORD dwCount) { ASSERT(pDWords && dwCount); REAL *pReals = new REAL[dwCount]; REAL *pDest = pReals; REAL *pLimit = pDest + dwCount; while (pDest<pLimit) { *pDest++ = (REAL)*pDWords++; } return pReals; } /// /// Translate GDI clipping modes into GDI+ equivalent. /// CombineMode SCClipModeFormRGNMode(INT iMode) { switch (iMode) { case RGN_COPY: return CombineModeReplace; case RGN_AND: return CombineModeIntersect; case RGN_DIFF: return CombineModeExclude; case RGN_OR: return CombineModeUnion; case RGN_XOR: return CombineModeXor; default: ASSERT(0); } return CombineModeReplace; } /// /// Perfom a rop opeartion by filling a rectangle with the color located at /// the given index in the default system palette. (WHITENESS/BLACKNESS) /// void SCSysPaletteFillRect(I_SCDCGraphics* pIGraphics, Rect& destRect, UINT uiPalIndex) { ASSERT(pIGraphics); ASSERT(uiPalIndex<256); PALETTEENTRY PaletteEntry; // Bogus GDI documentation?: index 1 doesn't correspond to WHITENESS as expected // (And we know that the 20 static colors (reserved) use the following indices: // 0-9, 246-255; but that's true for Windows 3.0 and 3.1; for other systems, !!??) // // So, consider using manual colors. HDC hdc = GetDC(NULL); GetSystemPaletteEntries (hdc, uiPalIndex, 1, &PaletteEntry); ReleaseDC(NULL, hdc); Color BrushColor; BrushColor.SetFromCOLORREF(pIGraphics->SCGetFinalColor(RGB(PaletteEntry.peRed, PaletteEntry.peGreen, PaletteEntry.peBlue))); SolidBrush brush(BrushColor); Graphics* pGraphics = pIGraphics->SCGetGraphics(); ASSERT(pGraphics); pGraphics->FillRectangle(&brush, destRect); } /// /// Perfom a DIB blt opeartion using ROP. /// INT SCStretchDIBits( I_SCDCGraphics* pIGraphics, // pointer to destination graphics context INT XDest, // x-coordinate of upper-left corner of dest. rectangle INT YDest, // y-coordinate of upper-left corner of dest. rectangle INT nDestWidth, // width of destination rectangle INT nDestHeight, // height of destination rectangle INT XSrc, // x-coordinate of upper-left corner of source rectangle INT YSrc, // y-coordinate of upper-left corner of source rectangle INT nSrcWidth, // width of source rectangle INT nSrcHeight, // height of source rectangle CONST VOID *lpBits, // address of bitmap bits CONST BITMAPINFO *lpBitsInfo, // address of bitmap data UINT iUsage, // usage flags DWORD dwRop // raster operation code ) { ASSERT(pIGraphics); // Prepare the temporary destination bitmap HDC hOutputDC = pIGraphics->SCGetDC(); ASSERT(hOutputDC); if (!hOutputDC) return FALSE; HDC hDestDC = CreateCompatibleDC(hOutputDC); #if 0 // best quality (but memory hog, and time consuming for GDI+, not for GDI) INT iTmpWidth = nSrcWidth; INT iTmpHeight = nSrcHeight; #else // faster (but less smooth image) INT iTmpWidth = abs(nSrcWidth); INT iTmpHeight = abs(nSrcHeight); if (iTmpWidth>=1000 || iTmpHeight>=1000) // TODO: fix these arbitrary values {// large high resolution bitmaps will bog down GDI+ // (it won't crash, but it can take 5 minutes, and generate 250Mo memory peaks) iTmpWidth = abs(nDestWidth); iTmpHeight = abs(nDestHeight); // inflate dest to enhance quality if (iTmpWidth<500 && iTmpHeight<500) { iTmpWidth *= 2; iTmpHeight *= 2; } } #endif HBITMAP hDestBmp = CreateCompatibleBitmap(hOutputDC, iTmpWidth, iTmpHeight); if (!hDestBmp) { DWORD dwError = GetLastError(); ASSERT(0); } HBITMAP hOldBmp = (HBITMAP)SelectObject(hDestDC, hDestBmp); // Copy the destination rectangle BOOL bOK = StretchBlt(hDestDC, 0, 0, iTmpWidth, iTmpHeight, hOutputDC, XDest, YDest, nDestWidth, nDestHeight, SRCCOPY); if (!bOK) {// Maybe failure due to rotation/shear found in hOutputDC // (do a manual resampling of the destination rectangle) float pitchx = float(nDestWidth)/float(iTmpWidth); float pitchy = float(nDestHeight)/float(iTmpHeight); int ys; for (int y=0; (y<iTmpHeight); y++) { ys = (int)(YDest + y*pitchy); int xs; for (int x=0; (x<iTmpWidth); x++) { xs = (int)(XDest + x*pitchx); COLORREF crColor = GetPixel(hOutputDC, xs, ys); SetPixel(hDestDC, x, y, crColor); } } } pIGraphics->SCReleaseDC(hOutputDC); // Ensure that the brush is in destination dc HBRUSH hBrush = NULL; HBRUSH hOldBrush = NULL; Brush* pBrush = pIGraphics->SCGetBrush(); // brush of destination if (pBrush) { Color BrushColor; switch(pBrush->GetType()) { case BrushTypeSolidColor: ((SolidBrush*)pBrush)->GetColor(&BrushColor); hBrush = CreateSolidBrush(BrushColor.ToCOLORREF()); break; case BrushTypeHatchFill: ((HatchBrush*)pBrush)->GetBackgroundColor(&BrushColor); hBrush = CreateSolidBrush(BrushColor.ToCOLORREF()); break; } } // else NULL brush, let default brush in place. // TODO: check that the ROP doesn't use the brush if (hBrush) hOldBrush = (HBRUSH)SelectObject(hDestDC, hBrush); // Merge source and dest (this is not smoothed) StretchDIBits(hDestDC, 0, 0, iTmpWidth, iTmpHeight, XSrc, YSrc, nSrcWidth, nSrcHeight, lpBits, lpBitsInfo, iUsage, dwRop); // We are done with dest DC if (hBrush) { SelectObject(hDestDC, hOldBrush); DeleteObject(hBrush); } SelectObject(hDestDC, hOldBmp); DeleteDC(hDestDC); // Create GDI+ bitmap { Bitmap Bmp(hDestBmp, (HPALETTE)NULL); ASSERT(Bmp.GetLastStatus()==Ok); // GDI+ drawing (smoothed a little according to graphics settings) Rect destRect(XDest, YDest, nDestWidth, nDestHeight); pIGraphics->SCDrawBitmap(&Bmp, destRect); } // Clean up DeleteObject(hDestBmp); return TRUE; } /// /// Compute a normalized rectangle to use in GDI+ functions. /// (GDI's twisted rectangles would result in negative width/height, confusing GDI+) /// void SCNormalizedRectFromRECT(Rect* pRect, LPCRECT pRc) { ASSERT(pRect && pRc); if (pRc->right < pRc->left) { pRect->X = pRc->right; pRect->Width = pRc->left - pRc->right; } else { pRect->X = pRc->left; pRect->Width = pRc->right - pRc->left; } if (pRc->bottom < pRc->top) { pRect->Y = pRc->bottom; pRect->Height = pRc->top - pRc->bottom; } else { pRect->Y = pRc->top; pRect->Height = pRc->bottom - pRc->top; } } /// /// Compute clockwise angles to use in GDI+ functions from GDI's basic information. /// void SCBuildArcAngles(LPCRECTL pBox, LPCPOINTL pPtStart, LPCPOINTL pPtEnd, INT iArcDir, double& dStartAngle, double& dSweepAngle) { double dCenterX = (pBox->right + pBox->left)/2; double dCenterY = (pBox->bottom + pBox->top)/2; dStartAngle = SC_DEGREES(atan2((pPtStart->y - dCenterY), pPtStart->x - dCenterX)); double dEndAngle = SC_DEGREES(atan2((pPtEnd->y - dCenterY), pPtEnd->x - dCenterX)); dSweepAngle = (dEndAngle - dStartAngle); // arc direction is used (AD_COUNTERCLOCKWISE or AD_CLOCKWISE) if (iArcDir==AD_CLOCKWISE) {// AD_CLOCKWISE for GDI if (dSweepAngle<=0) {// Negative values are counterclockwise for GDI+. So get the angle's negative complement. dSweepAngle = -(360 + dSweepAngle); // counterclockwise for GDI+ } // else clockwise for GDI+ } else {// AD_COUNTERCLOCKWISE for GDI if (dSweepAngle>=0) {// Positive values are clockwise for GDI+. So get the angle's negative complement. dSweepAngle = -(360 - dSweepAngle); // counterclockwise for GDI+ } // else clockwise for GDI+ } } /// /// Intersection of a cercle define by its center and radius, and a line passing by this center. /// (Angle is in degrees) /// void SCIntersectLineAndCircle(Point& PtRes, POINTL* pPtCenter, DWORD dwRadius, double fLineAngle) { double dAngle = -SC_RAD(fLineAngle); PtRes.X = static_cast<INT>(pPtCenter->x + dwRadius*cos(dAngle)); PtRes.Y = static_cast<INT>(pPtCenter->y + dwRadius*sin(dAngle)); } /// /// Intersection of an ellipse define by a rectangle, and a line passing by its center. /// (Angle is in degrees) /// void SCIntersectLineAndEllipse(Point& PtRes, LPCRECTL pBox, double fLineAngle) { int xPtCenter = (pBox->left + pBox->right)/2; int yPtCenter = (pBox->top + pBox->bottom)/2; double a = (pBox->right - pBox->left); double b = (pBox->bottom - pBox->top); double dAngle = -SC_RAD(fLineAngle); double a_sinAngle = a*sin(dAngle); double b_cosAngle = b*cos(dAngle); double d = sqrt(a_sinAngle*a_sinAngle + b_cosAngle*b_cosAngle); PtRes.X = static_cast<INT>(xPtCenter + (a*b_cosAngle/d)); PtRes.Y = static_cast<INT>(yPtCenter + (b*a_sinAngle/d)); } HBRUSH SCBrushFromGdipImage(Image* pImage) { ASSERT(pImage); ASSERT(pImage->GetType()==ImageTypeBitmap); int iWidth = pImage->GetWidth(); int iHeight = pImage->GetHeight(); HDC hdc = CreateCompatibleDC(NULL); HBITMAP hbmp = NULL; BitmapData BmData; Rect rect(0, 0, iWidth, iHeight); Status iRes = ((Bitmap*)pImage)->LockBits(&rect, ImageLockModeRead, PixelFormat32bppARGB, &BmData); ASSERT(iRes==Ok); if (iRes==Ok) { BITMAPINFOHEADER bmih; bmih.biSize = sizeof(BITMAPINFOHEADER); bmih.biWidth = iWidth; bmih.biHeight = iHeight; // bottom-up (beware of sign) bmih.biPlanes = 1; bmih.biBitCount = 32; bmih.biCompression = BI_RGB; bmih.biClrUsed = 0; // just set the rest to 0 bmih.biSizeImage = 0; bmih.biXPelsPerMeter = 0; bmih.biYPelsPerMeter = 0; bmih.biClrImportant = 0; bmih.biSizeImage = iWidth * iHeight * 4; LPVOID pBits; hbmp = CreateDIBSection(hdc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, &pBits, NULL, 0); ASSERT(hbmp); if (hbmp) { SetDIBits(hdc, hbmp, 0, iHeight, (CONST VOID *)BmData.Scan0, (BITMAPINFO*)&bmih, DIB_RGB_COLORS); } } ((Bitmap*)pImage)->UnlockBits(&BmData); // Warning: May create resource leak, as we don't call DeleteObject on hbmp HBRUSH hBrush = CreatePatternBrush(hbmp); DeleteDC(hdc); return hBrush; } /// /// Perfom a pattern blt opeartion using ROP. /// INT SCMergeAreaWithPattern( I_SCDCGraphics* pIGraphics, // pointer to destination graphics context Rect& destRect, // dest. rectangle DWORD dwRop) // raster operation code { ASSERT(pIGraphics); // Prepare the temporary destination bitmap HDC hOutputDC = pIGraphics->SCGetDC(); ASSERT(hOutputDC); if (!hOutputDC) return FALSE; HDC hDestDC = CreateCompatibleDC(hOutputDC); INT nDestWidth = destRect.Width; // width of destination rectangle INT nDestHeight = destRect.Height; // height of destination rectangle HBITMAP hDestBmp = CreateCompatibleBitmap(hOutputDC, nDestWidth, nDestHeight); ASSERT(hDestBmp); if (!hDestBmp) { DWORD dwError = GetLastError(); return FALSE; } HBITMAP hOldBmp = (HBITMAP)SelectObject(hDestDC, hDestBmp); // Copy the destination rectangle if (!StretchBlt(hDestDC, 0, 0, nDestWidth, nDestHeight, hOutputDC, destRect.X, destRect.Y, nDestWidth, nDestHeight, SRCCOPY)) {// Maybe failure for rotation/shear found in hOutputDC // (do a manual copy of the destination rectangle) int ys = destRect.Y; for (int y=0; (y<nDestHeight); y++, ys++) { int xs = destRect.X; for (int x=0; (x<nDestWidth); x++, xs++) { COLORREF Color = GetPixel(hOutputDC, xs, ys); SetPixel(hDestDC, x, y, Color); } } } pIGraphics->SCReleaseDC(hOutputDC); // Ensure that the brush is in destination dc HBRUSH hBrush = NULL; Brush* pBrush = pIGraphics->SCGetBrush(); // brush of destination if (pBrush) { Color BrushColor; switch(pBrush->GetType()) { case BrushTypeSolidColor: ((SolidBrush*)pBrush)->GetColor(&BrushColor); hBrush = CreateSolidBrush(BrushColor.ToCOLORREF()); break; case BrushTypeHatchFill: ((HatchBrush*)pBrush)->GetBackgroundColor(&BrushColor); hBrush = CreateSolidBrush(BrushColor.ToCOLORREF()); break; case BrushTypeTextureFill: { Image* pImage = ((TextureBrush*)pBrush)->GetImage(); ASSERT(pImage); hBrush = SCBrushFromGdipImage(pImage); delete pImage; } break; default: ASSERT(0); // TODO } } // else NULL brush, let default brush in place. // TODO: check that the ROP doesn't use the brush // Merge source and dest (this is not smoothed) if (hBrush) { HBRUSH hOldBrush = (HBRUSH)SelectObject(hDestDC, hBrush); PatBlt(hDestDC, 0, 0, nDestWidth, nDestHeight, dwRop); SelectObject(hDestDC, hOldBrush); DeleteObject(hBrush); } else { PatBlt(hDestDC, 0, 0, nDestWidth, nDestHeight, dwRop); } // We are done with dest DC SelectObject(hDestDC, hOldBmp); DeleteDC(hDestDC); // Create GDI+ bitmap { Bitmap Bmp(hDestBmp, (HPALETTE)NULL); ASSERT(Bmp.GetLastStatus()==Ok); pIGraphics->SCDrawBitmap(&Bmp, destRect); } // Clean up DeleteObject(hDestBmp); return TRUE; } /////////////////////////////////////////////////////////////////////////////////////////// // The algorithm of this function was adapted from XPDF // (Patrick Moreau, Martin P.J. Zinser, (c) Glyph & Cog, LLC) // // "kludge for polygon fills: First, it divides up the [subpolys] into // non-overlapping polygons by simply comparing bounding rectangles. // Then it connects [subpolys] within a single compound polygon to a single // point so that [GDI+] can fill the polygon (sort of)." // Point* SCConvertPolyPoly(Point* pPolyPoints, DWORD& numPoints, DWORD* pPolyVertices, DWORD& dwNbPolys) { Point* pPoints = pPolyPoints; // allocate new points int size = numPoints + dwNbPolys*2; // add 2 more points by poly Point* pPolyPts = new Point[size]; // allocate bounding rectangles array RECT *rects = new RECT[dwNbPolys]; // rebuild each subpoly numPoints = 0; Point Pt0(0, 0); Point* pLimit = pPoints; for (int iPoly = 0; (iPoly < (int)dwNbPolys); ++iPoly) { // add the points int iFirstPt = numPoints; pLimit += pPolyVertices[iPoly]; for (; (pPoints < pLimit); pPoints++) { pPolyPts[numPoints++] = *pPoints; } // construct bounding rectangle rects[iPoly].left = rects[iPoly].right = pPolyPts[iFirstPt].X; rects[iPoly].top = rects[iPoly].bottom = pPolyPts[iFirstPt].Y; for (int k = iFirstPt + 1; (k < (int)numPoints); ++k) { if (pPolyPts[k].X < rects[iPoly].left) rects[iPoly].left = pPolyPts[k].X; else if (pPolyPts[k].X > rects[iPoly].right) rects[iPoly].right = pPolyPts[k].X; if (pPolyPts[k].Y < rects[iPoly].top) rects[iPoly].top = pPolyPts[k].Y; else if (pPolyPts[k].Y > rects[iPoly].bottom) rects[iPoly].bottom = pPolyPts[k].Y; } // close subpoly if necessary if (pPolyPts[numPoints-1].X != pPolyPts[iFirstPt].X || pPolyPts[numPoints-1].Y != pPolyPts[iFirstPt].Y) { // add point pPolyPts[numPoints++] = pPolyPts[iFirstPt]; } // length of this subpoly pPolyVertices[iPoly] = numPoints - iFirstPt; // leave an extra point for compound fill hack pPolyPts[numPoints++] = Pt0; } // combine compound polygons RECT rect; // temp rect for overlaps detection DWORD dwNewNbPolys = 0; // new polygons counter int iCurPoly = 0; // index of current polygon to examine or merge int iOverlap = 0; // index of a polygon overlapping iCurPoly int iFirstPt = 0; // index of first point in iCurPoly int iLastPt = 0; // index of last point in iCurPoly while (iCurPoly < (int)dwNbPolys) { // start a new polygon with the iCurPoly subpoly rect = rects[iCurPoly]; pPolyVertices[dwNewNbPolys] = pPolyVertices[iCurPoly]; iFirstPt = iLastPt; iLastPt += pPolyVertices[iCurPoly] + 1; // +1 for the extra point pPolyPts[iLastPt - 1] = pPolyPts[iFirstPt]; // combine overlapping polygons ++iCurPoly; do { // look for the first subsequent subpoly, if any, which overlaps for (iOverlap = iCurPoly; (iOverlap < (int)dwNbPolys); ++iOverlap) { if (rects[iOverlap].right > rects[iCurPoly].left && rects[iOverlap].left < rects[iCurPoly].right && rects[iOverlap].bottom > rects[iCurPoly].top && rects[iOverlap].top < rects[iCurPoly].bottom) { // there is an overlap, combine the polygons // (by anchoring them at the first point of current polygon) for (; (iCurPoly <= iOverlap); ++iCurPoly) { if (rects[iCurPoly].left < rect.left) rect.left = rects[dwNewNbPolys].left; if (rects[iCurPoly].right > rect.right) rect.right = rects[dwNewNbPolys].right; if (rects[iCurPoly].top < rect.top) rect.top = rects[dwNewNbPolys].top; if (rects[iCurPoly].bottom > rect.bottom) rect.bottom = rects[dwNewNbPolys].bottom; // merge points pPolyVertices[dwNewNbPolys] += pPolyVertices[iCurPoly] + 1; // anchor this subpoly iLastPt += pPolyVertices[iCurPoly] + 1; pPolyPts[iLastPt - 1] = pPolyPts[iFirstPt]; } // exit to check next overlap with this compound polygon break; } } } while (iOverlap < (int)dwNbPolys && iCurPoly < (int)dwNbPolys); ++dwNewNbPolys; } // free bounding rectangles delete [] rects; dwNbPolys = dwNewNbPolys; return pPolyPts; } ////////////////////////////////////////////////////////////////////////////////////////// /// /// Create a GDI+ list of PathPointTypes from a list of GDI point types. /// BYTE* SCPathPointTypesFROMCurveTypes(LPCBYTE pGDITypes, DWORD dwCount) { ASSERT(pGDITypes); BYTE *pTypes = new BYTE[dwCount]; if (pTypes) { memmove(pTypes, pGDITypes, dwCount); SCConvertGDIPointTypes(pTypes, dwCount); } return pTypes; } /// /// Convert (in place) a list of GDI point types to GDI+ PathPointTypes. /// void SCConvertGDIPointTypes(LPBYTE pTypes, DWORD dwCount) { ASSERT(pTypes); // convert path point types for (DWORD i=0; (i<dwCount); i++) { // GDI+ documentation states: // "The PathPointType enumeration indicates point types and flags for the data points // in a path. Bits 0 through 2 indicate the type of a point, and bits 3 through 7 // hold a set of flags that specify attributes of a point." // Hence the dirty cast of an enum into BYTE; // Note that no function takes an array of PathPointType in the GraphicsPath class. switch (pTypes[i] & SC_PATH_PTMASK) { case PT_MOVETO: pTypes[i] = (BYTE)PathPointTypeStart; break; case PT_LINETO: if (pTypes[i] & SC_PATH_CLOSEFIG) pTypes[i] = (BYTE)(PathPointTypeLine|PathPointTypeCloseSubpath); else pTypes[i] = (BYTE)PathPointTypeLine; break; case PT_BEZIERTO: if (pTypes[i] & SC_PATH_CLOSEFIG) pTypes[i] = (BYTE)(PathPointTypeBezier|PathPointTypeCloseSubpath); else pTypes[i] = (BYTE)PathPointTypeBezier; break; } } } /// /// Subtype of an EMF: EmfTypeEmfOnly|EmfTypeEmfPlusOnly|EmfTypeEmfPlusDual /// EmfType SCGdipGetEMFType(HENHMETAFILE hemf) { Metafile metafile(hemf); MetafileHeader header; metafile.GetMetafileHeader(&header); switch(header.GetType()) { case MetafileTypeEmfPlusOnly: return EmfTypeEmfPlusOnly; case MetafileTypeEmfPlusDual: return EmfTypeEmfPlusDual; } return EmfTypeEmfOnly; } /// /// Any image to Enhanced Metafile format /// BOOL SCGdipPlayMetafile(HDC hDCPlay, HENHMETAFILE hemf, LPCRECT pRect) { ASSERT(hDCPlay); ASSERT(hemf); ASSERT(pRect); Metafile metafile(hemf); MetafileHeader header; metafile.GetMetafileHeader(&header); Graphics graphics(hDCPlay); Rect destRec(pRect->left, pRect->top, PRECT_WIDTH(pRect), PRECT_HEIGHT(pRect)); graphics.DrawImage(&metafile, destRec); return (graphics.GetLastStatus()==Ok); } /// /// Any GDIp image to Enhanced Metafile format /// HENHMETAFILE SCConvertImagetoEMF(Image& rImage) { // Store image in metafile. // Use printer DC to avoid loss in resolution BOOL bPrnDC = FALSE; #if 1 HDC hdc = SCGetDefaultPrinterDC(NULL); if (hdc) bPrnDC = TRUE; else { hdc = GetDC(NULL); // accept loss in resolution if (!hdc) return NULL; } #else // test for screen dc HDC hdc = GetDC(NULL); if (!hdc) return NULL; #endif // Note: this, sometimes, generates images bigger or smaller than expected. // Some JPEGs are completely blured. Where is my part in this bug? Metafile metafile(hdc, EmfTypeEmfOnly); ASSERT(metafile.GetLastStatus()==Ok); {// we want the graphics object to detach itself from the metafile before we // extract the GDI handle Graphics graphics(&metafile); ASSERT(graphics.GetLastStatus()==Ok); graphics.DrawImage(&rImage, 0, 0); ASSERT(graphics.GetLastStatus()==Ok); } HENHMETAFILE hEMF = metafile.GetHENHMETAFILE(); ASSERT(metafile.GetLastStatus()==Ok); if (bPrnDC) DeleteDC(hdc); else ReleaseDC(NULL, hdc); return hEMF; } /// /// Any (gdi+ supported) image file to Enhanced Metafile format /// HENHMETAFILE SCConvertImagetoEMF(LPCTSTR lpszFname) { // load image #ifdef _UNICODE Image image(lpszFname); #else WCHAR wFileName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, (LPSTR)lpszFname, _tcslen(lpszFname)+1, wFileName, MAX_PATH); Image image(wFileName); #endif ASSERT(image.GetLastStatus()==Ok); return SCConvertImagetoEMF(image); } /// /// Bitmap to Enhanced Metafile format /// HENHMETAFILE SCGDIpConvertBitmaptoEMF(HBITMAP hbm, HPALETTE hpal) { Bitmap bmp(hbm, hpal); ASSERT(bmp.GetLastStatus()==Ok); return SCConvertImagetoEMF(bmp); } /// /// Any piece of memory containing an image to Enhanced Metafile format /// HENHMETAFILE SCGDIpConvertImagetoEMF(HANDLE hMem) { HENHMETAFILE hEMF = NULL; IStream* pIStream = NULL; HRESULT hr = ::CreateStreamOnHGlobal((HGLOBAL)hMem, FALSE, &pIStream); if ((hr == S_OK) && pIStream) { Image img(pIStream, TRUE); if (img.GetLastStatus()==Ok) hEMF = SCConvertImagetoEMF(img); pIStream->Release(); } return hEMF; } int MSGetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; // number of image encoders UINT size = 0; // size of the image encoder array in bytes ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return -1; // Failure pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return -1; // Failure GetImageEncoders(num, size, pImageCodecInfo); for(UINT j = 0; j < num; ++j) { if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; // Success } } free(pImageCodecInfo); return -1; // Failure } BOOL SCGDIpSaveImage(HBITMAP hbm, LPCTSTR lpszPathname, INT iType, INT iJPEGQuality/*=100*/) { ASSERT(hbm); EncoderParameters encoderParameters; ULONG quality; EncoderParameters* pParameters = NULL; // Check encoder CLSID clsidEncoder; INT iEncoder; switch (iType & SC_SUBTYPE_MASK) { case SC_SUBTYPE_IMG_JPG: iEncoder = MSGetEncoderClsid(L"image/jpeg", &clsidEncoder); // optimistically prepare quality encoderParameters.Count = 1; encoderParameters.Parameter[0].Guid = EncoderQuality; encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong; encoderParameters.Parameter[0].NumberOfValues = 1; quality = iJPEGQuality; encoderParameters.Parameter[0].Value = &quality; pParameters = &encoderParameters; break; case SC_SUBTYPE_IMG_BMP: iEncoder = MSGetEncoderClsid(L"image/bmp", &clsidEncoder); break; case SC_SUBTYPE_IMG_PNG: iEncoder = MSGetEncoderClsid(L"image/png", &clsidEncoder); break; case SC_SUBTYPE_IMG_GIF: iEncoder = MSGetEncoderClsid(L"image/gif", &clsidEncoder); break; case SC_SUBTYPE_IMG_TIFF: iEncoder = MSGetEncoderClsid(L"image/tiff", &clsidEncoder); break; default: { ASSERT(0); return FALSE; } } if (iEncoder<0) return FALSE; // render image Bitmap image(hbm, (HPALETTE)NULL); #ifdef _UNICODE Status lRes = image.Save(lpszPathname, &clsidEncoder, pParameters); #else WCHAR wPathName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, (LPSTR)lpszPathname, _tcslen(lpszPathname)+1, wPathName, MAX_PATH); Status lRes = image.Save(wPathName, &clsidEncoder, pParameters); #endif return (lRes == Ok); } /// /// Rotate and translate Graphics so that the origin is at (rect.left, rect.top) /// BOOL SCRotateGraphics(Graphics* pGraphics, int iAngle, CRect rect, int iXPos, int iYPos) { ASSERT(pGraphics); BOOL bOk = FALSE; XFORM xform; memset(&xform, 0, sizeof(xform)); // Formulas: //xform.eM11 = (float)cos((float)iAngle*(ST_PI)/180.0f); => 0 for PI/2 and 3PI/2, 1 for 0 and PI //xform.eM12 = (float)sin((float)iAngle*(ST_PI)/180.0f); => 0 for 0 and PI, 1 for PI/2 and 3PI/2 // rotate and translate at once switch (iAngle) { case 90: xform.eM11 = 0; xform.eM12 = 1; xform.eDx = (float)(rect.bottom + iXPos); xform.eDy = (float)(-rect.left + iYPos); break; case 270: xform.eM11 = 0; xform.eM12 = -1; xform.eDx = (float)(-rect.top + iXPos); xform.eDy = (float)(rect.right + iYPos); break; case 180: xform.eM11 = -1; xform.eM12 = 0; xform.eDx = (float)(rect.right + iXPos); xform.eDy = (float)(rect.bottom + iYPos); break; case 0: xform.eM11 = 1; xform.eM12 = 0; xform.eDx = (float)(-rect.left + iXPos); xform.eDy = (float)(-rect.top + iYPos); break; default: ASSERT(0); } xform.eM22 = xform.eM11; xform.eM21 = -xform.eM12; Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy); pGraphics->SetTransform(&matrix); bOk = (pGraphics->GetLastStatus()==Ok); ASSERT(bOk); return bOk; } BOOL CALLBACK SCGDIpFlatEnumMetafile(Gdiplus::EmfPlusRecordType recordType, UINT flags, UINT uiEMFRecDataSize, const BYTE* pEMFRecData, VOID* pCallbackData) { Metafile* pMetafile = (Metafile*)pCallbackData; pMetafile->PlayRecord(recordType, flags, uiEMFRecDataSize, pEMFRecData); return TRUE; } /// /// Convert a GDI EMF to a GDI+ (scaled, rotated, etc...) EMF /// /// WARNING: This does NOT WORK! (complex ROP codes fail,...) /// HENHMETAFILE SCBBoxConvertEMFtoEMFp(HENHMETAFILE hEMF, SCGDIpDrawingAttributes& rDrawingAttributes, EmfType eType, CRect& rcDest, CRect& rcMeta, CRect& PaperRect, HDC hDCPlay, REAL iZoom, INT iAngle, I_SCRenderingContext* pIPostRender/*=NULL*/) { #pragma message( __FILE__ "(1721): TODO: Fix SCBBoxConvertEMFtoEMFp ") ASSERT(hEMF); CSize sizeEMF; SCGetEMFPlaySize(hEMF, sizeEMF); hDCPlay = CreateCompatibleDC(NULL); HBITMAP hbm = CreateCompatibleBitmap(hDCPlay, sizeEMF.cx, sizeEMF.cy); HBITMAP hOldBm = (HBITMAP)SelectObject(hDCPlay, hbm); //PRB1: the 81 dpi syndrome? (see SC_USING_TRICK81 in SCEMFImage.cpp) // Rect frameRect(rcMeta.left, rcMeta.top, rcMeta.Width(), rcMeta.Height()); // Metafile* pMeta = new Metafile(hDCPlay, frameRect, MetafileFrameUnitGdi, eType); UNUSED(rcMeta); Rect frameRect(rcDest.left, rcDest.top, rcDest.Width(), rcDest.Height()); Metafile* pMeta = new Metafile(hDCPlay, frameRect, MetafileFrameUnitPixel, eType); ASSERT(pMeta->GetLastStatus()==Ok); // Play EMF in pMeta. int iPlayX = rcDest.left - PaperRect.left; int iPlayY = rcDest.top - PaperRect.top; CRect rcPlay(iPlayX, iPlayY, iPlayX + iZoom*sizeEMF.cx, iPlayY + iZoom*sizeEMF.cy); #if 0 //PRB2: renderer does not work. BOOL bUseRenderer = (SCGdipGetEMFType(hEMF)!=EmfTypeEmfPlusOnly); #else BOOL bUseRenderer = FALSE; #endif // Note: Graphics object must be detached from the metafile before we can retrieve // the metafile handle; of course, post renderer must be called before detachment. if (bUseRenderer) { // Clip ::IntersectClipRect(hDCPlay, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom); // Rotate int iGrOldMode; if (iAngle) {// Position of the PAGE rectangle for rotation in world coordinates CRect rcRotate = PaperRect; rcRotate.OffsetRect(iPlayX, iPlayY); SCRotateDC(hDCPlay, iAngle, rcRotate, rcDest.left, rcDest.top, iGrOldMode); } // Parse CSCEMFmetaDCRenderer renderer(pMeta); rDrawingAttributes.bPrinting = FALSE; renderer.SCSetDrawingAttributes(rDrawingAttributes); CSCEMFgdiParser parser(&renderer); parser.SCParse(hEMF, hDCPlay, (LPRECT)&rcPlay); if (pIPostRender) { // restore the pre-rendering state to get a clean graphics parser.SCRestorePreRenderingState(); GDPGraphics* pGraphics = renderer.SCGetGraphics(); ASSERT(pGraphics); // post render pIPostRender->SCPostRender(hDCPlay, NULL, pGraphics); } } else {// Draw only // Note: this too has problems; a combination of 2 images (one with ROP:SRCPAINT // the second with ROP:SRCAND) produces a black image. // Where is this my responsibility in this bug? Metafile metafile(hEMF); Graphics graphics(pMeta); // Clip // You know what? GDI+ finds a way to place this clipping instruction in the metafile // AFTER the following FillRectangle. Rect rectClip(rcDest.left, rcDest.top, RECT_WIDTH(rcDest), RECT_HEIGHT(rcDest)); graphics.SetClip(rectClip, CombineModeReplace); // Background if (rDrawingAttributes.bBkSolid) { Color BrushColor; BrushColor.SetFromCOLORREF(rDrawingAttributes.crPaperColor); SolidBrush brush(BrushColor); rectClip.Inflate(1, 1); // include border; otherwise clipping will be lost (!?) graphics.FillRectangle(&brush, rectClip); } // else clipping is lost // Rotate if (iAngle) { CRect rcRotate = PaperRect; rcRotate.OffsetRect(iPlayX, iPlayY); SCRotateGraphics(&graphics, iAngle, rcRotate, rcDest.left, rcDest.top); } // Draw #if 0 // a function is missing. I give up here. ImageAttributes imgattributes; if (rDrawingAttributes.crPaperColor!=RGB(255, 255, 255)) { ColorMap clrMap; clrMap.oldColor = Color(255, 255, 255, 255); clrMap.newColor = BrushColor; imgattributes.SetRemapTable(1, &clrMap); } RectF rectImage(rcPlay.left, rcPlay.top, rcPlay.Width(), rcPlay.Height()); // 1) call not understood // graphics.DrawImage(&metafile, rectImage, // rcMeta.X/100.0, rcMeta.Y/100.0, // rcMeta.Width/100.0, rcMeta.Height/100.0, // UnitMillimeter, // &imgattributes, NULL, NULL); // 2) data is falling into ... metafile // graphics.EnumerateMetafile(&metafile, rectImage, // SCGDIpFlatEnumMetafile, &metafile, &imgattributes); // 3) and we can't enumerate accross metafiles // graphics.EnumerateMetafile(&metafile, rectImage, // SCGDIpFlatEnumMetafile, pMeta, &imgattributes); // The PlayRecord function should be a member // of Graphics, not of Metafile. // Now, my son, you see the power of a DC? #else // Here, we are losing the remaining drawing attributes Rect rectImage(rcPlay.left, rcPlay.top, rcPlay.Width(), rcPlay.Height()); graphics.DrawImage(&metafile, rectImage); if (pIPostRender) { pIPostRender->SCPostRender(hDCPlay, NULL, &graphics); } #endif } HENHMETAFILE hEMFp = pMeta->GetHENHMETAFILE(); delete pMeta; pMeta = NULL; SelectObject(hDCPlay, hOldBm); DeleteObject(hbm); DeleteDC(hDCPlay); ASSERT(hEMFp); return hEMFp; } /// /// Convert a GDI EMF to a GDI+ EMF (unscaled and no border) /// /// WARNING: This does NOT WORK! /// HENHMETAFILE SCConvertEMFtoEMFp(HENHMETAFILE hEMF, SCGDIpDrawingAttributes& rDrawingAttributes, EmfType eType) { #pragma message( __FILE__ "(1859): TODO: Fix SCConvertEMFtoEMFp") ASSERT(hEMF); ENHMETAHEADER EmfHeader; if (!::GetEnhMetaFileHeader(hEMF, sizeof(ENHMETAHEADER), &EmfHeader)) return NULL; CSize sizeEMF; SCGetEMFPlaySize(hEMF, sizeEMF); HDC hDCPlay = CreateCompatibleDC(NULL); HBITMAP hbm = CreateCompatibleBitmap(hDCPlay, sizeEMF.cx, sizeEMF.cy); HBITMAP hOldBm = (HBITMAP)SelectObject(hDCPlay, hbm); //PRB1: the 81 dpi syndrome? (see SC_USING_TRICK81 in SCEMFImage.cpp) // Rect frameRect(0, 0, RECT_WIDTH(EmfHeader.rclFrame), RECT_HEIGHT(EmfHeader.rclFrame)); // Metafile* pMeta = new Metafile(hDCPlay, frameRect, MetafileFrameUnitGdi, eType); Rect frameRect(0, 0, sizeEMF.cx, sizeEMF.cy); Metafile* pMeta = new Metafile(hDCPlay, frameRect, MetafileFrameUnitPixel, eType); ASSERT(pMeta->GetLastStatus()==Ok); // Play EMF in pMeta. CRect rcPlay(0, 0, sizeEMF.cx, sizeEMF.cy); //PRB2: renderer does not work. BOOL bUseRenderer = (SCGdipGetEMFType(hEMF)==EmfTypeEmfOnly); // Note: Graphics object must be detached from the metafile before we can retrieve // the metafile handle if (bUseRenderer) { ::IntersectClipRect(hDCPlay, rcPlay.left, rcPlay.top, rcPlay.right, rcPlay.bottom); CSCEMFmetaDCRenderer renderer(pMeta); rDrawingAttributes.bPrinting = FALSE; renderer.SCSetDrawingAttributes(rDrawingAttributes); CSCEMFgdiParser parser(&renderer); parser.SCParse(hEMF, hDCPlay, (LPRECT)&rcPlay); } else {// Draw only (for example to just convert to/from GDI+) Metafile metafile(hEMF); Graphics graphics(pMeta); // background (can be deactivated before calling this function) if (rDrawingAttributes.bBkSolid) { Color BrushColor; BrushColor.SetFromCOLORREF(rDrawingAttributes.crPaperColor); SolidBrush brush(BrushColor); Rect rectBkgn(rcPlay.left, rcPlay.top, rcPlay.Width(), rcPlay.Height()); graphics.FillRectangle(&brush, rectBkgn); } // draw Rect destRec(rcPlay.left, rcPlay.top, rcPlay.Width(), rcPlay.Height()); graphics.DrawImage(&metafile, destRec); } HENHMETAFILE hEMFp = pMeta->GetHENHMETAFILE(); delete pMeta; SelectObject(hDCPlay, hOldBm); DeleteObject(hbm); DeleteDC(hDCPlay); return hEMFp; }